﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Net;
using System.IO;
using System.Xml.Linq;
using System.Data;
using System.Threading;

using MyMoviesBrowserLibrary;
using System.ComponentModel;

namespace TrailersDownloader
{        
    public delegate void DownloadProgressChangedEvent(DownloadProgressChangedEventArgs e);
    public delegate void StatusChangedEvent(string status);
    public delegate void DownloaderEvent();
    public class Downloader
    {
        public event DownloadProgressChangedEvent DownloadProgressChanged;
        public event StatusChangedEvent StatusChanged;
        public event DownloaderEvent Complete;
        public event DownloaderEvent DownloadStarted;

        private ManualResetEvent waitHandle;
        const string TRAILERSXML = "http://www.apple.com/trailers/home/xml/current.xml";
        private string tempdir = "temp";

        private WebClient client;
        private string tempPath;
        private Settings settings;
        
        MyMoviesBrowserDataContext ctx;

        BackgroundWorker worker;

        Queue<XElement> xMovies;
        Queue<Trailer> trailerQueue;
        List<MovieTrailer> movieTrailers;

        bool canceled = false;

        public Downloader()
        {
            settings = Settings.Default;
            waitHandle = new ManualResetEvent(true);
            xMovies = new Queue<XElement>();
            trailerQueue = new Queue<Trailer>();
            movieTrailers = new List<MovieTrailer>();
            ctx = MyMoviesBrowserBusiness.GetNewMyMoviesBrowserDataContext();

            client = new WebClient();
            client.DownloadProgressChanged += new DownloadProgressChangedEventHandler(client_DownloadProgressChanged);
            client.DownloadFileCompleted += new System.ComponentModel.AsyncCompletedEventHandler(client_DownloadFileCompleted);

            worker = new BackgroundWorker();
            worker.WorkerSupportsCancellation = true;
            worker.WorkerReportsProgress = true;
            worker.DoWork += new DoWorkEventHandler(worker_DoWork);
            worker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(worker_RunWorkerCompleted);
            worker.ProgressChanged += new ProgressChangedEventHandler(worker_ProgressChanged);
        }

        void worker_ProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            if (StatusChanged != null)
                StatusChanged(e.UserState.ToString());
        }

        void worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            if (Complete != null)
                Complete();
        }

        void worker_DoWork(object sender, DoWorkEventArgs e)
        {
            StartDownload();
        }

        public void Start()
        {
            worker.RunWorkerAsync();
        }

        private void StartDownload()
        {
            worker.ReportProgress(0, "Checking Directory");
            tempdir = System.IO.Path.Combine(settings.TrailersDownloadFolder, tempdir);
            if (!Directory.Exists(tempdir))
            {
                worker.ReportProgress(0, "Creating Directory");
                Directory.CreateDirectory(tempdir);
            }

            worker.ReportProgress(0, "Loading XML");
            XDocument doc = XDocument.Load(TRAILERSXML);

            worker.ReportProgress(0, "Processing XML");
            var q = from x in doc.Descendants("movieinfo")
                    select x;
            foreach (XElement movieInfo in q)
            {
                xMovies.Enqueue(movieInfo);
            }
            worker.ReportProgress(0, "Processing Trailers");
            ProcessMovieQueue();
        }

        private void SaveMovieInfo(XElement movieInfo)
        {
            if (!DownloadTrailer(movieInfo))
            {
                ProcessMovieQueue();
                return;
            }

            string safeTitle = Regex.Replace(movieInfo.Element("info").Element("title").Value, "[\\/:*?\"<>|]", "");
            if (safeTitle.ToLower().StartsWith("the "))
                safeTitle = safeTitle.Substring(4) + ", The";


            worker.ReportProgress(0, string.Format("Processing '{0}'", safeTitle));

            if (safeTitle.EndsWith("."))
                safeTitle = safeTitle.Substring(0, safeTitle.Length - 1);
            string moviePath = System.IO.Path.Combine(settings.TrailersDownloadFolder, safeTitle);

            if (!Directory.Exists(moviePath))
                Directory.CreateDirectory(moviePath);

            string posterPath = System.IO.Path.Combine(moviePath, "folder.jpg");
            tempPath = System.IO.Path.Combine(tempdir, safeTitle + ".jpg");

            movieInfo.Save(System.IO.Path.Combine(moviePath, "movie.xml"));

            if (!File.Exists(posterPath) && !string.IsNullOrEmpty(movieInfo.Element("poster").Element("location").Value))
            {
                worker.ReportProgress(0, string.Format("Downloading {0} Poster: ", movieInfo.Element("info").Element("title").Value));
                try
                {
                    client.DownloadFile(new Uri(movieInfo.Element("poster").Element("location").Value), tempPath);
                    File.Move(tempPath, posterPath);
                }
                catch
                {
                    if (File.Exists(tempPath))
                        File.Delete(tempPath);
                }
            }


            var q2 = from t in movieInfo.Descendants("preview")
                     where t.Element("large") != null
                     select new Trailer()
                     {
                         Url = t.Element("large").Value
                     };

            MovieTrailer trailer = MyMoviesBrowserBusiness.ImportFolder(moviePath);
            movieTrailers.Add(trailer);

            foreach (Trailer t in q2)
            {
                t.Url = GetTrailerUrl(t.Url);
                t.FileName = System.IO.Path.GetFileName(t.Url);
                t.FilePath = System.IO.Path.Combine(moviePath, t.FileName);
                t.TempPath = System.IO.Path.Combine(tempdir, t.FileName);
                string tempFileName = t.FilePath.Replace(".mov", ".wmv");

                if (!File.Exists(t.FilePath) && !File.Exists(tempFileName))
                {
                    trailerQueue.Enqueue(t);
                    MyMoviesBrowserBusiness.MarkTrailerAsWatched(trailer, false);
                }
            }

            ProcessTrailerQueue();
        }

        private void CleanUp()
        {
            worker.ReportProgress(0, "Cleaning up trailers");

            var trailers = ctx.MovieTrailers.ToList();

            foreach (MovieTrailer trailer in movieTrailers)
            {
                MovieTrailer temp = trailers.SingleOrDefault(t => t.MovieInfoId == trailer.MovieInfoId);
                if (temp != null)
                    trailers.Remove(temp);
            }

            foreach (MovieTrailer trailer in trailers)
            {
                if (!string.IsNullOrEmpty(trailer.MovieFolder) && Directory.Exists(trailer.MovieFolder))
                    Directory.Delete(trailer.MovieFolder, true);

                ctx.MovieTrailer_Genres.DeleteAllOnSubmit(trailer.MovieTrailer_Genres);
                ctx.MovieTrailer_Persons.DeleteAllOnSubmit(trailer.MovieTrailer_Persons);
                ctx.MovieTrailers.DeleteOnSubmit(trailer);
            }

            ctx.SubmitChanges();

            if (!Directory.Exists(tempdir))
                Directory.Delete(tempdir, true);


            worker.ReportProgress(0, "Complete");
            if (Complete != null)
                Complete();
        }

        private void ProcessMovieQueue()
        {
            if (xMovies.Count > 0)
            {
                SaveMovieInfo(xMovies.Dequeue());
            }
            else
            {
                CleanUp();
            }
        }

        private void ProcessTrailerQueue()
        {
            if (trailerQueue.Count > 0)
            {
                Trailer trailer = trailerQueue.Dequeue();
                worker.ReportProgress(0, string.Format("Downloading {0}", trailer.FileName));
                waitHandle.Reset();
                if (DownloadStarted != null)
                    DownloadStarted();
                client.DownloadFileAsync(new Uri(trailer.Url), trailer.TempPath, trailer);
                waitHandle.WaitOne();
                ProcessTrailerQueue();
            }
            else
            {
                ProcessMovieQueue();
            }
        }

        private string GetTrailerUrl(string trailerUrl)
        {
            string temp = "";
            Stream stream;
            if (Settings.Default.DownloadHD)
            {
                if (Settings.Default.Try1080)
                {
                    temp = trailerUrl.Replace("640w", "1080p");
                    try
                    {
                        stream = client.OpenRead(temp);
                        stream.Close();
                        return temp;
                    }
                    catch { }
                }

                temp = trailerUrl.Replace("640w", "720p");
                try
                {
                    stream = client.OpenRead(temp);
                    stream.Close();
                    return temp;
                }
                catch { }
            }
            return trailerUrl;
        }

        void client_DownloadFileCompleted(object sender, System.ComponentModel.AsyncCompletedEventArgs e)
        {

            Trailer t = e.UserState as Trailer;
            try
            {
                if (!canceled)
                    File.Move(t.TempPath, t.FilePath);
            }
            catch
            {

            }
            if (File.Exists(t.TempPath))
                File.Delete(t.TempPath);
            if (!canceled)
                waitHandle.Set();
                //ProcessTrailerQueue();
        }


        void client_DownloadProgressChanged(object sender, DownloadProgressChangedEventArgs e)
        {
            if (DownloadProgressChanged != null)
                DownloadProgressChanged(e);

            //if (InvokeRequired)
            //    Invoke(new UpdateProgressDel(UpdateProgress), new object[] { e.ProgressPercentage });
            //else
            //    UpdateProgress(e.ProgressPercentage);

            //this.Dispatcher.BeginInvoke(new UpdateProgressDel(UpdateProgress), new object[] { e.TotalBytesToReceive, e.BytesReceived });
        }

        //delegate void UpdateProgressDel(int percent);
        //void UpdateProgress(int percent)
        //{
        //    currentProgressBar.Value = percent;
        //}

        bool DownloadTrailer(XElement xMovie)
        {
            if (xMovie.Element("info") != null)
            {
                XElement info = xMovie.Element("info");
                if (info.Element("rating") != null)
                {
                    string strRating = info.Element("rating").Value;

                    switch (strRating)
                    {
                        case "R":
                            return settings.DownloadR;
                        case "PG-13":
                            return settings.DownloadPG13;
                        case "PG":
                            return settings.DownloadPG;
                        case "G":
                            return settings.DownloadG;
                        default:
                            return settings.DownloadNotYetRated;
                    }

                }
            }
            return true;
        }


        public void Cancel()
        {
            canceled = true;
            if (client.IsBusy)
                client.CancelAsync();
            worker.CancelAsync();
            if (Complete != null)
                Complete();
        }


        class Trailer
        {
            public string FileName { get; set; }
            public string FilePath { get; set; }
            public string Url { get; set; }
            public string TempPath { get; set; }
        }
    }

}
